<template>
  <div class="contBody">
    <h2 class="title">地图导航</h2>
    <div id="navigation" class="map__x">
      <div id="container">
        <p>位置点坐标（position）: <code id="position"></code></p>
        <p>
          位置精度（position accuracy）: <code id="positionaccuracy"></code>
        </p>
        <p>当前方向（heading）: <code id="heading"></code></p>
        <p>当前速度（speed）: <code id="speed"></code></p>
        <p>采样周期（delta）: <code id="delta"></code></p>
      </div>
    </div>
    <div class="btnGroup">
      <el-button type="primary" :disabled="disabled" @click="simulateBtn"
        >开始导航</el-button
      >
    </div>
    <div id="info" class="alert alert-error" style="display: none"></div>
    <img
      id="geolocation_marker"
      src="../../assets/images/geolocation_marker_heading.png"
    />
  </div>
</template>

<script>
import { Map, View, Overlay, Geolocation } from "ol";
import Tile from "ol/layer/Tile";
import LineString from "ol/geom/LineString";
import XYZ from "ol/source/XYZ";
import "ol/ol.css";

import simulationData from "@/assets/json/geolocation-orientation.json";
import geolocationMarkerHeading from "@/assets/images/geolocation_marker_heading.png";
import geolocationMarker from "@/assets/images/geolocation_marker.png";

export default {
  name: "MapPage",
  data() {
    return {
      map: null,
      disabled: false,
    };
  },
  mounted() {
    this.initMap();
  },
  methods: {
    initMap() {
      const view = new View({
        projection: "EPSG:4326",
        center: [5.8713, 45.6452],
        minZoom: 5,
        zoom: 9,
      });

      // 地图实例
      this.map = new Map({
        target: "navigation", // 对应页面里 id 为 map 的元素
        layers: [
          // 图层
          new Tile({
            source: new XYZ({
              url: "http://t0.tianditu.com/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=200d3f62135e4debe4f843220883e31d",
              wrapX: false,
            }),
            zIndex: 1,
            visible: true,
          }),
          new Tile({
            source: new XYZ({
              url: "http://t0.tianditu.com/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=200d3f62135e4debe4f843220883e31d",
              wrapX: false,
            }),
            zIndex: 1,
            visible: true,
          }),
        ],
        view: view,
      });

      this.positions = new LineString([], "XYZM");

      // 创建导航定位控件（Geolocation Control）
      this.geolocation = new Geolocation({
        projection: view.getProjection(), //投影坐标系
        trackingOptions: {
          maximumAge: 10000, //最大周期
          enableHighAccuracy: true, //启用高精度
          timeout: 600000, //超时
        },
      });

      //创建导航定位标识的标注，添加到地图中
      const markerEl = document.getElementById("geolocation_marker");
      const marker = new Overlay({
        positioning: "center-center",
        element: markerEl,
        stopEvent: false,
      });
      this.map.addOverlay(marker);

      let deltaMean = 500; // 地理位置变化采样周期（MS）
      // 定位控件信息变化监听事件（添加定位点并更新轨迹面板信息）
      this.geolocation.on("change", () => {
        let position = this.geolocation.getPosition(); //获取定位点
        let accuracy = this.geolocation.getAccuracy(); //获取位置精度
        let heading = this.geolocation.getHeading() || 0; //获取当前航向
        let speed = this.geolocation.getSpeed() || 0; //获取当前速度
        let m = Date.now();
        this.addPosition(position, heading, m, speed); //添加定位点
        let coords = this.positions.getCoordinates(); //定位点的坐标
        let len = coords.length;
        if (len >= 2) {
          deltaMean = (coords[len - 1][3] - coords[0][3]) / (len - 1);
        }
        //更新信息面板的定位点相关信息
        const positionDom = document.getElementById("position");
        const positionaccuracyDom = document.getElementById("positionaccuracy");
        const headingDom = document.getElementById("heading");
        const speedDom = document.getElementById("speed");
        const deltaDom = document.getElementById("delta");
        positionDom.innerText =
          position[0].toFixed(2) + ", " + position[1].toFixed(2);
        positionaccuracyDom.innerText = accuracy + " m";
        headingDom.innerText = Math.round(this.radToDeg(heading)) + "&deg;";
        speedDom.innerText = (speed * 3.6).toFixed(1) + " km/h";
        deltaDom.innerText = Math.round(deltaMean) + "ms";
      });
      // 导航控件错误事件
      this.geolocation.on("error", () => {
        alert("this.geolocation error");
      });

      let previousM = 0;
      // 地图渲染前改变中心与旋转角度
      this.map.on("precompose", (map) => {
        let frameState = map.frameState;
        if (frameState !== null) {
          // 利用地理位置变化采样周期平稳变换过渡
          let m = frameState.time - deltaMean * 1.5;
          m = Math.max(m, previousM);
          previousM = m;
          // 沿模拟轨迹设置当前定位点的position
          let c = this.positions.getCoordinateAtM(m, true);
          let view = frameState.viewState;
          if (c) {
            view.center = this.getCenterWithHeading(c, -c[2], view.resolution); //设置中心点
            view.rotation = -c[2]; //设置地图旋转角度
            marker.setPosition(c); //设置导航定位点的标注位置
          }
        }
        return true;
      });
    },

    simulateBtn() {
      const simulateBtn = document.getElementById("simulate");
      if (simulationData == null || simulationData == undefined) {
        alert("未成功加载模拟数据！");
        return;
      }
      const coordinates = simulationData.data; //模拟轨迹点数组
      const first = coordinates.shift(); //删除数组第一个元素并返回第一个元素
      this.simulatePositionChange(first); //执行模拟位置变换功能
      let prevDate = first.timestamp; //默认第一个点位点的时间戳

      let geolocateFun = () => {
        let position = coordinates.shift(); //删除数组第一个元素并返回第一个元素
        if (!position) {
          simulateBtn.disabled = ""; //开启功能按钮
          return;
        }
        let newDate = position.timestamp; //当前定位点时间戳
        this.simulatePositionChange(position); //执行模拟位置变换功能
        //创建一个定时器，每隔一定时间执行定位功能函数
        window.setTimeout(() => {
          prevDate = newDate;
          geolocateFun();
        }, (newDate - prevDate) / 0.5);
      };

      geolocateFun(); //执行定位功能
      this.map.on("postcompose", this.render); //为地图容器添加postcompose事件监听
      this.map.render(); //渲染地图
      this.disabled = true;
    },
    simulatePositionChange(position) {
      let coords = position.coords; //定位点坐标
      this.geolocation.set("accuracy", coords.accuracy); //设置位置精度
      this.geolocation.set("heading", this.degToRad(coords.heading)); //设置当前航向值
      let position_ = [coords.longitude, coords.latitude];
      let projectedPosition = position_;
      this.geolocation.set("position", projectedPosition); //设置定位点坐标
      this.geolocation.set("speed", coords.speed); //设置当前速度
      this.geolocation.changed();
    },
    render() {
      this.map.render(); //渲染地图
    },
    addPosition(position, heading, m, speed) {
      const markerEl = document.getElementById("geolocation_marker");
      let x = position[0];
      let y = position[1];
      let fCoords = this.positions.getCoordinates();
      let previous = fCoords[fCoords.length - 1];
      let prevHeading = previous && previous[2];
      if (prevHeading) {
        let headingDiff = heading - this.mod(prevHeading);
        // 强制变换角度（旋转角度变化不超过180度）
        if (Math.abs(headingDiff) > Math.PI) {
          let sign = headingDiff >= 0 ? 1 : -1;
          headingDiff = -sign * (2 * Math.PI - Math.abs(headingDiff));
        }
        heading = prevHeading + headingDiff;
      }
      this.positions.appendCoordinate([x, y, heading, m]);
      //只保留20个最后的坐标
      this.positions.setCoordinates(this.positions.getCoordinates().slice(-20));
      if (heading && speed) {
        markerEl.src = geolocationMarkerHeading;
      } else {
        markerEl.src = geolocationMarker;
      }
    },
    //弧度转度
    radToDeg(rad) {
      return (rad * 360) / (Math.PI * 2);
    },
    //度转弧度
    degToRad(deg) {
      return (deg * Math.PI * 2) / 360;
    },
    //负值模
    mod(n) {
      return ((n % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI);
    },
    getCenterWithHeading(position, rotation, resolution) {
      const size = this.map.getSize();
      const height = size[1];
      return [
        position[0] - (Math.sin(rotation) * height * resolution * 1) / 4,
        position[1] + (Math.cos(rotation) * height * resolution * 1) / 4,
      ];
    },
  },
};
</script>

<style lang="css" scoped>
.contBody .title {
  padding: 0 0 24px;
}
.map__x {
  width: 100%;
  height: 600px;
  border: 1px solid #eee;
}

.btnGroup {
  text-align: left;
}

#container {
  position: absolute;
  top: 190px;
  right: 20px;
  width: 24%;
  height: 120px;
  padding: 10px;
  background-color: rgba(0, 0, 0, 0.5);
  font-size: 14px;
  border-radius: 2px;
  z-index: 999;
}

#container p {
  color: rgb(255, 255, 255);
  line-height: 20px;
  text-align: left;
}

#container code {
  color: rgb(240, 244, 45);
  font: bold;
}

#mapCon {
  position: relative;
  background: #f8f4f0;
  width: 100%;
  height: 100%;
}

#menu {
  padding: 10px;
  border-radius: 3px;
  overflow: hidden;
  width: 100%;
  height: auto;
  box-shadow: 0 0 2px 1px #949292;
  text-align: center;
  z-index: 2001;
  border-color: #66ccff;
  border-bottom: 1px solid #949292;
  margin-bottom: 2px;
}

#info {
  color: rgba(255, 0, 0, 0.5);
}
</style>
